#include "Renderer.h"

// Glew.
#define GLEW_STATIC
#include <glew.h>
#ifdef _WIN32
#include <wglew.h>
#endif
// OpenGl.
#ifdef _WIN32
#include <GL/GL.h>
#else
#include <GL/gl.h>
#endif
// SDL.
#include <SDL.h>

// Renderer.
#include "ShaderProgram.h"
#include "Camera.h"
#include "Model.h"

// CoreLib.
#include "StartupStruct.h"
#include "Logging.h"
#include "Memory.h"
#include "FileReader.h"

namespace
{
	bool CreateShader(const GLenum shaderType, const char* source, GLuint& shaderOutput)
	{
		/* Create an empty vertex shader handle */
		shaderOutput = glCreateShader(shaderType);

		/* Send the vertex shader source code to GL */
		/* Note that the source code is NULL character terminated. */
		/* GL will automatically detect that therefore the length info can be 0 in this case (the last parameter) */
		glShaderSource(shaderOutput, 1, (const GLchar**)&source, 0);

		/* Compile the vertex shader */
		glCompileShader(shaderOutput);

		int isShaderCompiled;
		glGetShaderiv(shaderOutput, GL_COMPILE_STATUS, &isShaderCompiled);
		if(isShaderCompiled == GL_FALSE)
		{
			TODO("Add output here");
			int maxLenght;
			glGetShaderiv(shaderOutput, GL_INFO_LOG_LENGTH, &maxLenght);

			/* The maxLength includes the NULL character */
			char* vertexInfoLog = (char *)malloc(maxLenght);

			glGetShaderInfoLog(shaderOutput, maxLenght, &maxLenght, vertexInfoLog);

			/* Handle the error in an appropriate way such as displaying a message or writing to a log file. */
			/* In this simple program, we'll just leave */
			free(vertexInfoLog);

			return false;
		}
		return true;
	}

	bool CreateShaderProgram(const GLuint vertexShader, const GLuint fragmentShader, GLuint& shaderProgramOutput)
	{
		/* If we reached this point it means the vertex and fragment shaders compiled and are syntax error free. */
		/* We must link them together to make a GL shader program */
		/* GL shader programs are monolithic. It is a single piece made of 1 vertex shader and 1 fragment shader. */
		/* Assign our program handle a "name" */
		shaderProgramOutput = glCreateProgram();

		/* Attach our shaders to our program */
		glAttachShader(shaderProgramOutput, vertexShader);
		glAttachShader(shaderProgramOutput, fragmentShader);

		/* Bind attribute index 0 (coordinates) to in_Position and attribute index 1 (color) to in_Color */
		/* Attribute locations must be setup before calling glLinkProgram. */
		glBindAttribLocation(shaderProgramOutput, 0, "inputPosition");
		glBindAttribLocation(shaderProgramOutput, 1, "inputColor");

		/* Link our program */
		/* At this stage, the vertex and fragment programs are inspected, optimized and a binary code is generated for the shader. */
		/* The binary code is uploaded to the GPU, if there is no error. */
		glLinkProgram(shaderProgramOutput);

		/* Again, we must check and make sure that it linked. If it fails, it would mean either there is a mismatch between the vertex */
		/* and fragment shaders. It might be that you have surpassed your GPU's abilities. Perhaps too many ALU operations or */
		/* too many texel fetch instructions or too many interpolators or dynamic loops. */

		int isLinked;
		glGetProgramiv(shaderProgramOutput, GL_LINK_STATUS, (int *)&isLinked);
		if(isLinked == GL_FALSE)
		{
			TODO("Handle this!");
			/* Noticed that glGetProgramiv is used to get the length for a shader program, not glGetShaderiv. */
			int maxLength = 0;
			glGetProgramiv(shaderProgramOutput, GL_INFO_LOG_LENGTH, &maxLength);

			/* The maxLength includes the NULL character */
			char* shaderProgramInfoLog = (char *)malloc(maxLength);

			/* Notice that glGetProgramInfoLog, not glGetShaderInfoLog. */
			glGetProgramInfoLog(shaderProgramOutput, maxLength, &maxLength, shaderProgramInfoLog);

			/* Handle the error in an appropriate way such as displaying a message or writing to a log file. */
			/* In this simple program, we'll just leave */
			free(shaderProgramInfoLog);
			return false;
		}
		return true;
	}
}

le::Renderer::Renderer()
{
	m_Camera = LeNew le::Camera();
}

le::Renderer::~Renderer()
{
	LeDelete(m_Camera);
	m_Camera = NULL;
}

void le::Renderer::DestroyRenderer()
{
}

void le::Renderer::Present()
{
	SwapWindow();
}

void le::Renderer::CreateRenderer( const le::StartupStruct& startupStruct )
{
	/* Request an opengl 3.2 context.
     * SDL doesn't have the ability to choose which profile at this time of writing,
     * but it should default to the core profile */
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
 
    /* Turn on double buffering with a 24bit Z buffer.
     * You may need to change this to 16 or 32 for your system */
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 32);

	m_Window = startupStruct.m_Window;
	SDL_GL_SetSwapInterval(1);

	/* Create our opengl context and attach it to our window */
	m_Context = SDL_GL_CreateContext(m_Window);

	TODO("Make this cleaner. So we can actually do some error checking etc.");
	glewExperimental = GL_TRUE; 
	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
		/* Problem: glewInit failed, something is seriously wrong. */
		const char* errorStr = (const char*)glewGetErrorString(err);
		LeOutputDebugString(errorStr);
	}
}

void le::Renderer::Clear( float r, float g, float b, float alpha )
{
	glClearColor(r, g, b, alpha);
	glClear(GL_COLOR_BUFFER_BIT);
}

void le::Renderer::SwapWindow()
{
	SDL_GL_SwapWindow(m_Window);
}

le::Model* le::Renderer::CreateModel()
{
	le::Model* outModel = new le::Model();
	outModel->InitBuffers();
	return outModel;
}

void le::Renderer::RenderModel( const le::Model* model )
{
	glBindVertexArray(model->GetVertexBufferId());

	// Render the vertex buffer using the index buffer.
	glDrawElements(GL_TRIANGLES, model->GetNumIndices(), GL_UNSIGNED_INT, 0);
}

void le::Renderer::MakeShaderActive( const uint32 shaderProgramId )
{
	glUseProgram(shaderProgramId);
}

le::ShaderProgram* le::Renderer::CreateShader( const le::Path& vs, const le::Path& fs )
{
	GLuint vertexShader;
	GLuint fragmentShader;

	GLuint shaderProgram;

	le::FileReader vsReader(vs, le::eFileReadType_Buffer);
	le::FileReader fsReader(fs, le::eFileReadType_Buffer);

	if (vsReader.IsOpened() && fsReader.IsOpened())
	{
		const char* vsBuffer = vsReader.GetFileBuffer();
		if(!::CreateShader(GL_VERTEX_SHADER, vsBuffer, vertexShader))
		{
			return 0;
		}

		const char* fsBuffer = fsReader.GetFileBuffer();
		if (!::CreateShader(GL_FRAGMENT_SHADER, fsBuffer, fragmentShader))
		{
			return 0;
		}

		if (!::CreateShaderProgram(vertexShader, fragmentShader, shaderProgram))
		{
			return 0;
		}

		return new le::ShaderProgram(fragmentShader, fragmentShader, shaderProgram);
	}

	return 0;
}
